<?php
/**--------------------------------------------------------------------------+
 * Project:     CuteView: the PHP template engine                            |
 * File:        CuteViewEngine.php                                           |
 *---------------------------------------------------------------------------+
 * This library is free software; you can redistribute it and/or             |
 * modify it under the terms of the GNU Lesser General Public                |
 * License as published by the Free Software Foundation; either              |
 * version 2.1 of the License, or (at your option) any later version.        |
 *                                                                           |
 * This library is distributed in the hope that it will be useful,           |
 * but WITHOUT ANY WARRANTY; without even the implied warranty of            |
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         |
 * Lesser General Public License for more details.                           |
 *                                                                           |
 * You should have received a copy of the GNU Lesser General Public          |
 * License along with this library; if not, write to the Free Software       |
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA |
 *                                                                           |
 * For questions, help, comments, discussion, etc., please contact the author|
 *---------------------------------------------------------------------------+
 * @copyright 2007-2007 blizzchris                                           |
 * @author Chris Yue <blizzchris@gmail.com>                                  |
 * @package CuteView                                                         |
 * @version 1.071222 beta                                                    |
 *---------------------------------------------------------------------------+
 */


abstract class CuteViewEngine
{
	/**
	 * template tag
	 *
	 * @var string
	 */
	public  $left_delimiter = "{#";
	
	/**
	 * template tag
	 *
	 * @var string
	 */
	public  $right_delimiter = "#}";
		
	/**
	 * set true to clear undefined variables
	 *
	 * @var bool
	 */
	public  $clear_undefined = true;
	
	/**
	 * set true to halt executing template file when php codes were found 
	 *
	 * @var bool
	 */
	public  $security_enhanced = true;

	
	/**----------------------------------+
	 * private or protected members	below|
	 * ----------------------------------*/
	/**
	 * template tag used for regular expression
	 *
	 * @var string
	 */
	protected $left_delimiter_reg;
	
	/**
	 * template tag used for regular expression
	 *
	 * @var string
	 */
	protected $right_delimiter_reg;
	
	/**
	 * templete files root path
	 *
	 * @var string
	 */
	protected $root_path;
	
	/**
	 * template file path
	 *
	 * @var string
	 */
	protected $file_path;
	
	/**
	 * variables => values
	 *
	 * @var array
	 */
	protected $values;
	
	/**
	 * contents to ouput
	 *
	 * @var string
	 */
	protected $content;
	
	/**
	 * the generated variable suffix
	 *
	 * @var string
	 */
	protected $suffix = "CuteViewValue";

	
	
	public function __construct()
	{
		
	}
	
	public function __destruct()
	{
		
	}
	
	/**
	 * set template files' root path
	 *
	 * @param string $root_path
	 */
	public function setRootPath($root_path)
	{
		$this -> root_path = rtrim($root_path, "/") . "/";
	}
	
	public function setFile($file_path)
	{
		$this -> file_path = $file_path;
	}
	
	/**
	 * assign values to template variables.
	 * $values = array( 'variable_name' => 'value' )
	 * it can mix with repeater values too,
	 * and the repeater will auto loop if the repeater has multi 'var' => 'value'
	 * for example, if you assign $value = array(
	 * 					'menu' => array(
	 * 							array('name' => 'home', 'href' => 'index.php'),
	 * 							array('name' => 'news', 'href' => 'news.php')
	 * 						)
	 * 				);
	 * the REPEATER menu will loop twice,
	 * and the it can also has multi-deep sub loop too
	 * if you assign the right $value!
	 *
	 * @param array $values
	 */
	public function assign($values)
	{
		foreach ( $values as $key => $val )
		{
			$this -> values[$key] = $val;
		}
	}
	
	public function setBlocksBehavior($blocks)
	{
		foreach ( $blocks as $block_name => $show )
		{
			$this -> setBlockBehavior($block_name, $show);
		}
	}
	
	public function setBlockBehavior($block_name, $show)
	{
		$this -> values[$block_name] = $show;
	}
	
	/**
	 * ouput the result!
	 * all you need to do 
	 * is simply set the right template file path
	 * 
	 */
	abstract public function display();
	
	/**
	 * get the result
	 * like display method 
	 * just set the right template file path!
	 *
	 * @return string
	 */
	public function fetch()
	{
		ob_start();
		$this -> display();
		return ob_get_clean();
	}
	
	
	/**----------------------------------+
	 * private or protected methods below|
	 * ----------------------------------*/
	/**
	 * handle the template file
	 *
	 * @param string $file_path
	 */
	protected function handleTemplate()
	{
		$this -> loadTemplate();
		$this -> parse();
	}
	
	/**
	 * execute the generated php codes
	 *
	 */
	protected function execute()
	{
		eval(" ?>{$this -> content}<?php ");
	}
	
	/**
	 * load the template content
	 *
	 */
	private function loadTemplate()
	{
		$fp = "{$this -> root_path}{$this -> file_path}";
		$this -> content = file_get_contents($fp);
	}
	
	/**
	 * handle the template content just loaded
	 *
	 */
	private function parse()
	{
		if ( $this -> security_enhanced )
		{
			$this -> securityCheck();
		}
		
		$this -> initDelimiterReg();
		$this -> replace();
		
		if ( $this -> clear_undefined )
		{
			$this -> clearUndefined();
		}
	}
	
	/**
	 * replace template repeater, block, variable with codes
	 *
	 */
	private function replace()
	{
		$this -> replaceValues();
		$this -> replaceRepeaters();
		$this -> replaceBlocks();
	}
	
	/**
	 * replace values with code
	 *
	 */
	private function replaceValues()
	{
		foreach ( $this -> values as $key => $value )
		{
			if ( !is_array($value) )
			{
				$this -> replaceValue($key);
			}
		}
	}
	
	/**
	 * replace value,
	 * in fact it just turns the tempate variable to php <?=..?> code
	 *
	 * @param string $key
	 */
	private function replaceValue($key)
	{
		$search = "{$this -> left_delimiter}$key{$this -> right_delimiter}";
		$replace = "<?=\$this -> values['$key']?>";
		$this -> content = str_replace($search, $replace, $this -> content);
	}
	
	/**
	 * replace REPEATERS with codes
	 *
	 */
	private function replaceRepeaters()
	{
		$this -> replaceRepeaterTags();
		$this -> replaceRepeaterValues();
	}
	
	/**
	 * replace repeater tags with php code
	 *
	 */
	private function replaceRepeaterTags()
	{
		$pattern = "|<!--REPEATER\[(.*)\]-->|U";
		preg_match_all($pattern, $this -> content, $out, PREG_PATTERN_ORDER);
		$repeater_names = $out[1];
		if ( !empty($repeater_names) )
		{
			foreach ( $repeater_names as $name )
			{
				$this -> replaceRepeaterTag($name);
			}
		}
	}
	
	/**
	 * replace REPEATER tag with php code
	 * in fact it just turn the template REPEATER to php foreach statement
	 *
	 * @param string $name
	 */
	private function replaceRepeaterTag($name)
	{
		if ( strpos($this -> content, "<!--REPEATER[$name]-->") < strpos($this -> content, "<!--/REPEATER[$name]-->") )
		{
			$search[] = "<!--REPEATER[$name]-->";
			$search[] = "<!--/REPEATER[$name]-->";
			if ( strpos($name, ".") > 0 )
			{
				$arr = explode(".", $name);
				$parent_key = $arr[0];
				$key = $arr[1];
				$replace[] = <<<rep
<?php 
	if ( isset(\${$parent_key}_CuteViewValue['{$key}']) ) : 
		\${$key} = \${$parent_key}_CuteViewValue['{$key}'];
		foreach ( \${$key} as \${$key}_{$this -> suffix} ) : 
?>
rep;
			}
			else 
			{
				$replace[] = <<<rep
<?php 
	if ( isset(\$this -> values['$name']) ) :
		\${$name} = \$this -> values['$name']; 
		foreach ( \${$name} as \${$name}_{$this -> suffix} ) : 
?>
rep;
			}
			$replace[] = "<?php endforeach; endif; ?>";
			$this -> content = str_replace($search, $replace, $this -> content);
		}
	}
	
	/**
	 * replace repeater values with code
	 *
	 */
	private function replaceRepeaterValues()
	{
		$pattern = "|{$this -> left_delimiter_reg}(.*){$this -> right_delimiter_reg}|U";
		preg_match_all($pattern, $this -> content, $out, PREG_PATTERN_ORDER);
		$repeater_vars = $out[1];
		foreach ( $repeater_vars as $var )
		{
			$this -> replaceRepeaterValue($var);
		}
	}
	
	/**
	 * replace repeater value with code
	 *
	 * @param string $var
	 */
	private function replaceRepeaterValue($var)
	{
		if ( strpos($var, ".") > 0 )
		{
			$search = "{$this -> left_delimiter}{$var}{$this -> right_delimiter}";
			$arr = explode(".", $var);
			$arr_name = $arr[0];
			$key = $arr[1];
			$replace = "<?=\${$arr_name}_{$this -> suffix}['$key']?>";
			$this -> content = str_replace($search, $replace, $this -> content);
		}
	}
	
	private function replaceBlocks()
	{
		$pattern = "|<!--BLOCK\[(.*)\]-->|U";
		preg_match_all($pattern, $this -> content, $out, PREG_PATTERN_ORDER);
		$block_names = $out[1];
		if ( !empty($block_names) )
		{
			foreach ( $block_names as $name )
			{
				$this -> replaceBlock($name);
			}
		}
	}
	
	private function replaceBlock($name)
	{
		if ( strpos($this -> content, "<!--BLOCK[$name]-->") < strpos($this -> content, "<!--/BLOCK[$name]-->") )
		{
			$search[] = "<!--BLOCK[$name]-->";	$replace[] = "<?php if ( \$this -> values['$name'] ) : ?>";
			$search[] = "<!--/BLOCK[$name]-->";	$replace[] = "<?php endif; ?>";
			$this -> content = str_replace($search, $replace, $this -> content);
		}
	}
	
	/**
	 * erase unsigned variable in template
	 *
	 */
	private function clearUndefined()
	{
		$pattern = "|{$this -> left_delimiter_reg}(.*){$this -> right_delimiter_reg}|U";
		$this -> content = preg_replace($pattern, "", $this -> content);
	}
	
	/**
	 * initial the delimiters to regular expression for $this -> replace()
	 *
	 */
	private function initDelimiterReg()
	{		
		$this -> left_delimiter_reg = preg_quote($this -> left_delimiter);
		$this -> right_delimiter_reg = preg_quote($this -> right_delimiter);
	}
	
	/**
	 * check the security of the template file content
	 * see if there is php codes.
	 *
	 */
	private function securityCheck()
	{
		if ( strpos($this -> content, '<?') > 0 || strpos($this -> content, '?>') > 0 )
		{
			die("<em>CuteView: Halt 'cause the template file includes some unsafe codes, you can set security_enhanced to be false to ignore this warning, not recommanded</em>");
		}
	}
	
}
